using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Configuration;

using Tao.OpenGl;

namespace tp2
{

    public class Curva 
    {
        private static int tramosVistaSuperior = int.Parse(ConfigurationManager.AppSettings["TramosVistaSuperior"]);
        private static int tramosVistaAlturas = int.Parse(ConfigurationManager.AppSettings["TramosVistaAlturas"]);
        private static int tramosSombra3D = int.Parse(ConfigurationManager.AppSettings["TramosSombra3D"]);
        private static int tramosCurva3D = int.Parse(ConfigurationManager.AppSettings["TramosCurva3D"]);

        public static int TramosCurva3D
        {
            get { return tramosCurva3D; }
        }

        private static MatrizBezier MatrizBezier = new MatrizBezier();
        private static MatrizBSpline MatrizBSpline = new MatrizBSpline();

        public static void BezierCubica(List<PuntoDeControl> puntosDeControl)
        {
            List<Punto> puntosCurva = CalcularPuntoBezierCubica(puntosDeControl, tramosVistaAlturas);

            if (puntosCurva.Count == 0)
                return;

            Gl.glDisable(Gl.GL_LIGHTING);
            ColorTP.GlColor(Color.RoyalBlue);
            Gl.glBegin(Gl.GL_LINE_STRIP);
            foreach (Punto punto in puntosCurva)
            {
                Gl.glVertex2d(punto.X, punto.Y);
            }
            Gl.glEnd();
            Gl.glEnable(Gl.GL_LIGHTING);

            if (puntosDeControl.Count > 0)
            {
                for (int i = 0; i < (puntosDeControl.Count - 1) / 3 + 1; i++)
                {
                    puntosDeControl[3 * i].dibujar();
                }
                DibujarTangentes(puntosDeControl);
            }
        }

        #region Calcular puntos bezier cubica
        private static List<Punto> CalcularPuntoBezierCubica(List<PuntoDeControl> puntosDeControl, int tramos)
        {
            List<Punto> puntosCurva = new List<Punto>();
            if (puntosDeControl.Count >= 4)
            {
                for (int tramo = 0; tramo < (puntosDeControl.Count - 1) / 3; tramo++)
                {
                    Matriz puntos = PuntoDeControl.ToArray(puntosDeControl.GetRange(tramo * 3, 4));
                    for (int i = 0; i <= tramos; i++)
                    {
                        Matriz m = (new VectorParametros(i / (float)tramos)) * MatrizBezier * puntos;
                        puntosCurva.Add(new Punto(m[0, 0], m[0, 1]));
                    }
                }
            }
            return puntosCurva;
        }
        #endregion

        #region Dibujar Tangentes
        private static void DibujarTangentes(List<PuntoDeControl> puntosDeControl)
        {
            List<PuntoDeControl> puntosTangentes = new List<PuntoDeControl>();
            Gl.glLineStipple(1, 0x0F0F);
            Gl.glEnable(Gl.GL_LINE_STIPPLE);
            Gl.glBegin(Gl.GL_LINES);
            for (int tramo = 0; tramo < (puntosDeControl.Count - 1) / 3; tramo++)
            {
                List<PuntoDeControl> puntosCurva = puntosDeControl.GetRange(tramo * 3, 4);
                Gl.glVertex2f(puntosCurva[0].X, puntosCurva[0].Y);
                Gl.glVertex2f(puntosCurva[1].X, puntosCurva[1].Y);
                Gl.glVertex2f(puntosCurva[2].X, puntosCurva[2].Y);
                Gl.glVertex2f(puntosCurva[3].X, puntosCurva[3].Y);
                puntosTangentes.Add(puntosCurva[1]);
                puntosTangentes.Add(puntosCurva[2]);
            }
            Gl.glEnd();
            Gl.glDisable(Gl.GL_LINE_STIPPLE);
        }
        #endregion

        public static void BSplineCubica(List<PuntoDeControl> puntosDeControl)
        {
            List<Punto> puntosCurva = CalcularPuntoBSplineCubica(puntosDeControl, tramosVistaSuperior);

            Gl.glDisable(Gl.GL_LIGHTING);
            ColorTP.GlColor(Color.RoyalBlue);
            Gl.glBegin(Gl.GL_LINE_STRIP);
            foreach (Punto punto in puntosCurva)
            {
                Gl.glVertex2d(punto.X, punto.Y);
            }
            Gl.glEnd();
            Gl.glEnable(Gl.GL_LIGHTING);

            foreach (PuntoDeControl punto in puntosDeControl)
            {
                punto.dibujar();
            }
            DibujarGrafoDeControl(puntosDeControl);
        }

        #region Calcular Punto BSpline Cubica
        private static List<Punto> CalcularPuntoBSplineCubica(List<PuntoDeControl> puntosDeControl, int tramos)
        {
            List<Punto> puntosCurva = new List<Punto>();
            if (puntosDeControl.Count >= 2)
            {
                
                for (int tramo = 0; tramo < puntosDeControl.Count; tramo++)
                {
                    Matriz puntos;
                    if (tramo < puntosDeControl.Count - 3)
                    {
                        puntos = PuntoDeControl.ToArray(puntosDeControl.GetRange(tramo, 4));
                    }
                    else
                    {
                        List<PuntoDeControl> listaPuntos = new List<PuntoDeControl>();
                        for (int i = 0; i < 4; i++)
                        {
                            listaPuntos.Add(puntosDeControl[(tramo + i) % puntosDeControl.Count]);
                        }
                        puntos = PuntoDeControl.ToArray(listaPuntos);
                    }

                    for (int i = 0; i <= tramos; i++)
                    {
                        Matriz m = (new VectorParametros(i / (float)tramos)) * MatrizBSpline * puntos;
                        puntosCurva.Add(new Punto(m[0, 0], m[0, 1]));
                    }
                }

            }
            return puntosCurva;

        }
        #endregion

        
        public static void BSplineCubica3D(List<PuntoDeControl> puntosDeControl)
        {
            List<PuntoDeControl> listaAux = new List<PuntoDeControl>();
            foreach (PuntoDeControl punto in puntosDeControl)
            {
                listaAux.Add(new PuntoDeControl((float)(punto.X - 0.5) * 2 * 18, (float)(punto.Y - 0.5) * 2 * 18));
            }
            List<Punto> puntosCurva = CalcularPuntoBSplineCubica(listaAux, tramosSombra3D);

            Gl.glDisable(Gl.GL_LIGHTING);
            ColorTP.GlColor(Color.RoyalBlue);
            Gl.glBegin(Gl.GL_LINE_STRIP);
            foreach (Punto punto in puntosCurva)
            {
                Gl.glVertex3d(punto.X, punto.Y, 0);
            }
            Gl.glEnd();
            Gl.glEnable(Gl.GL_LIGHTING);
        }

        #region DibujarGrafoControl
        internal static void DibujarGrafoDeControl(List<PuntoDeControl> puntosDeControl)
        {
            Gl.glLineStipple(1, 0x0F0F);
            Gl.glEnable(Gl.GL_LINE_STIPPLE);
            Gl.glBegin(Gl.GL_LINE_STRIP);
            foreach (PuntoDeControl punto in puntosDeControl)
            {
                Gl.glVertex2f(punto.X, punto.Y);
            }
            Gl.glEnd();
            Gl.glDisable(Gl.GL_LINE_STIPPLE);
        }
        #endregion

        public static List<Punto> Curva3D(List<PuntoDeControl> puntosDeControlVistaSuperior,
            List<PuntoDeControl> puntosDeControlVistaAltura)
        {
            if (puntosDeControlVistaAltura.Count == 0 || puntosDeControlVistaSuperior.Count == 0)
                return new List<Punto>();

            List<Punto> puntosCurvaXY;
            List<Punto> puntosCurvaZ = new List<Punto>();

            //Calculo de puntos curva XY
            List<PuntoDeControl> listaAux = new List<PuntoDeControl>();
            foreach (PuntoDeControl punto in puntosDeControlVistaSuperior)
            {
                listaAux.Add(new PuntoDeControl((float)(punto.X - 0.5) * 2* 18, (float)(punto.Y - 0.5) * 2 *18));
            }
            puntosCurvaXY = CalcularPuntoBSplineCubica(listaAux, tramosCurva3D);

            //Calculo de puntos curva Z
            puntosCurvaZ = CalcularPuntoBezierCubica(puntosDeControlVistaAltura, tramosCurva3D);

            if (puntosCurvaXY.Count == 0 || puntosCurvaZ.Count == 0)
                return new List<Punto>();
            
            int cont = 0;
            List<Punto> puntosCurva3D = new List<Punto>();
            foreach (Punto punto in puntosCurvaXY)
            {
                double altura = GetAltura(puntosCurvaZ, cont, puntosCurvaXY.Count);
                puntosCurva3D.Add(new Punto(punto.X, punto.Y, altura));
                cont++;
            }
            return puntosCurva3D;
        }

        private static double GetAltura(List<Punto> puntosCurvaZ, int i, int totalPuntos)
        {
            float paso = puntosCurvaZ.Count / (float)totalPuntos;

            int cotaInferior = (int)Math.Floor(i * paso);
            int cotaSuperior = cotaInferior + 1;

            if (cotaSuperior >= puntosCurvaZ.Count)
            {
                cotaSuperior = puntosCurvaZ.Count - 1;
            }

            double altura = ((puntosCurvaZ[cotaSuperior].Y - puntosCurvaZ[cotaInferior].Y) * (paso - (float)Math.Floor(paso))) + puntosCurvaZ[cotaInferior].Y;
            return altura * 5;
        }
    }
}
